package gwt.immunology.server.util.search;

import static gwt.immunology.shared.GenericDtoKeys.BIRTHDAY_FROM;
import static gwt.immunology.shared.GenericDtoKeys.BIRTHDAY_TO;
import static gwt.immunology.shared.GenericDtoKeys.REGISTRATION_FROM;
import static gwt.immunology.shared.GenericDtoKeys.REGISTRATION_TO;
import gwt.immunology.server.converter.PatientDtoConverter;
import gwt.immunology.shared.GenericDto;
import gwt.immunology.shared.enums.GenderEnum;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import models.MedicineCard_;
import models.Patient;
import models.Patient_;

public class PatientSearchUtil {

    public CriteriaQuery<Patient> createSearchCriteriaQuery(GenericDto searchCriteria) {
	CriteriaBuilder criteriaBuilder = Patient.em().getCriteriaBuilder();
	CriteriaQuery<Patient> criteriaQuery = criteriaBuilder.createQuery(Patient.class);

	Patient patient = PatientDtoConverter.convertGenericDtoToPatient(searchCriteria);

	Predicate searchPredicate = null;
	Root<Patient> patientRoot = criteriaQuery.from(Patient.class);

	Map<String, Path<String>> stringSearchParameters = this.getStringSearchParameters(patient, patientRoot);
	Map<List<Date>, Path<Date>> dateSearchParameters = this.getDateSearchParameters(searchCriteria, patientRoot);
	Map<Integer, Path<Integer>> integerSearchParameters = this.getIntegerSearchParameters(patient, patientRoot);
	Map<GenderEnum, Path<GenderEnum>> genderEnumSearchParameters = this.getGenderEnumSearchParameters(patient,
		patientRoot);
	boolean isFirstPredicate = true;
	if (stringSearchParameters != null && !stringSearchParameters.isEmpty()) {
	    Set<String> keySet = stringSearchParameters.keySet();
	    for (String parameter : keySet) {
		Path<String> path = stringSearchParameters.get(parameter);
		Predicate predicate = criteriaBuilder.like(criteriaBuilder.lower(path), parameter.toLowerCase() + "%");
		searchPredicate = this.extendPredicate(criteriaBuilder, searchPredicate, predicate, isFirstPredicate);
		isFirstPredicate = false;
	    }
	}
	if (genderEnumSearchParameters != null && !genderEnumSearchParameters.isEmpty()) {
	    Set<GenderEnum> keySet = genderEnumSearchParameters.keySet();
	    for (GenderEnum parameter : keySet) {
		Path<GenderEnum> path = genderEnumSearchParameters.get(parameter);
		Predicate predicate = criteriaBuilder.equal(path, parameter);
		searchPredicate = this.extendPredicate(criteriaBuilder, searchPredicate, predicate, isFirstPredicate);
		isFirstPredicate = false;
	    }
	}
	if (integerSearchParameters != null && !integerSearchParameters.isEmpty()) {
	    Set<Integer> keySet = integerSearchParameters.keySet();
	    for (Integer parameter : keySet) {
		Path<Integer> path = integerSearchParameters.get(parameter);
		Predicate predicate = criteriaBuilder.equal(path, parameter);
		searchPredicate = this.extendPredicate(criteriaBuilder, searchPredicate, predicate, isFirstPredicate);
		isFirstPredicate = false;
	    }
	}
	if (dateSearchParameters != null && !dateSearchParameters.isEmpty()) {
	    Set<List<Date>> dates = dateSearchParameters.keySet();
	    for (List<Date> parameter : dates) {
		Path<Date> path = dateSearchParameters.get(parameter);
		Date dateFrom = parameter.get(0);
		Date dateTo = parameter.get(1);
		if (dateFrom != null) {
		    Predicate predicate = criteriaBuilder.greaterThanOrEqualTo(path, dateFrom);
		    searchPredicate = this.extendPredicate(criteriaBuilder, searchPredicate, predicate,
			    isFirstPredicate);
		    isFirstPredicate = false;
		}
		if (dateTo != null) {
		    Predicate predicate = criteriaBuilder.lessThanOrEqualTo(path, dateTo);
		    searchPredicate = this.extendPredicate(criteriaBuilder, searchPredicate, predicate,
			    isFirstPredicate);
		    isFirstPredicate = false;
		}
	    }
	}
	criteriaQuery.where(searchPredicate);

	return criteriaQuery;
    }

    private Predicate extendPredicate(CriteriaBuilder criteriaBuilder, Predicate currentPredicate,
	    Predicate newPedicate, boolean isFirstPredicate) {
	Predicate result = null;
	if (isFirstPredicate) {
	    result = criteriaBuilder.and(newPedicate);
	} else {
	    result = criteriaBuilder.and(currentPredicate, newPedicate);
	}
	return result;
    }

    private Map<String, Path<String>> getStringSearchParameters(Patient patient, Root<Patient> patientRoot) {
	Map<String, Path<String>> result = new HashMap<String, Path<String>>();

	if (patient.firstName != null) {
	    result.put(patient.firstName, patientRoot.get(Patient_.firstName));
	}
	if (patient.middleName != null) {
	    result.put(patient.middleName, patientRoot.get(Patient_.middleName));
	}
	if (patient.lastName != null) {
	    result.put(patient.lastName, patientRoot.get(Patient_.lastName));
	}
	if (patient.country != null) {
	    result.put(patient.country, patientRoot.get(Patient_.country));
	}
	if (patient.region != null) {
	    result.put(patient.region, patientRoot.get(Patient_.region));
	}
	if (patient.district != null) {
	    result.put(patient.district, patientRoot.get(Patient_.district));
	}
	if (patient.city != null) {
	    result.put(patient.city, patientRoot.get(Patient_.city));
	}
	if (patient.street != null) {
	    result.put(patient.street, patientRoot.get(Patient_.street));
	}
	if (patient.house != null) {
	    result.put(patient.house, patientRoot.get(Patient_.house));
	}

	return result;
    }

    private Map<GenderEnum, Path<GenderEnum>> getGenderEnumSearchParameters(Patient patient, Root<Patient> patientRoot) {
	Map<GenderEnum, Path<GenderEnum>> result = new HashMap<GenderEnum, Path<GenderEnum>>();

	if (patient.gender != null) {
	    result.put(patient.gender, patientRoot.get(Patient_.gender));
	}

	return result;
    }

    private Map<Integer, Path<Integer>> getIntegerSearchParameters(Patient patient, Root<Patient> patientRoot) {
	Map<Integer, Path<Integer>> result = new HashMap<Integer, Path<Integer>>();

	if (patient.flat != null) {
	    result.put(patient.flat, patientRoot.get(Patient_.flat));
	}

	return result;
    }

    private Map<List<Date>, Path<Date>> getDateSearchParameters(GenericDto searchCriteria, Root<Patient> patientRoot) {
	Map<List<Date>, Path<Date>> result = new HashMap<List<Date>, Path<Date>>();

	List<Date> dates = null;
	Date birthdayFrom = searchCriteria.getDate(BIRTHDAY_FROM);
	Date birthdayTo = searchCriteria.getDate(BIRTHDAY_TO);
	Date registrationFrom = searchCriteria.getDate(REGISTRATION_FROM);
	Date registrationTo = searchCriteria.getDate(REGISTRATION_TO);

	if (birthdayFrom != null || birthdayTo != null) {
	    dates = new ArrayList<Date>();
	    dates.add(birthdayFrom);
	    dates.add(birthdayTo);
	    result.put(dates, patientRoot.get(Patient_.birthday));
	}

	if (registrationFrom != null || registrationTo != null) {
	    dates = new ArrayList<Date>();
	    dates.add(registrationFrom);
	    dates.add(registrationTo);
	    result.put(dates, patientRoot.get(Patient_.medicineCard).get(MedicineCard_.creationDate));
	}

	return result;
    }

}
